package main

import (
	"fmt"
	"unsafe"
)

type Dog struct {
	name string
}

func (dog *Dog) SetName(name string) {
	dog.name = name
}

func main() {
	//指针
	var dog *Dog = &Dog{name: "大黄"}
	fmt.Println(dog)  //&{大黄}
	fmt.Println(*dog) //{大黄}
	fmt.Println(&dog) //0xc000012028

	dog2 := Dog{"小黑"}
	fmt.Println(dog2)  //{小黑}
	fmt.Println(&dog2) //&{小黑}
	println()

	//不可寻址的值无法使用取址操作符&获取它们的指针，对不可寻址的值获取地址操作都会使编译器报错。
	//fmt.Println(Dog{"小黑"}.SetName("汪汪")) //报错: cannot call pointer method SetName on Dog

	// 利用unsafe.Pointer的中转  和  uintptr的底层操作  来操纵像dog这样的值
	dog3 := Dog{"泰迪"}                                       //声明一个Dog类型的变量dog3
	dog3Pointer := &dog3                                    //然后用取址操作符&取出它的指针值，并赋给了变量dog3Pointer
	dog3PointerData := uintptr(unsafe.Pointer(dog3Pointer)) //使用两个类型转换，先把dog3Pointer 转换成了一个unsafe.Pointer类型的值，然后继续转换成了一个uintptr的值
	fmt.Println(dog3PointerData)                            //824634142288

	namePtr := dog3PointerData + unsafe.Offsetof(dog3Pointer.name) //unsafe.Offsetof函数用于获取两个值在内存中的起始存储地址之间的偏移量，以字节为单位
	nameP := (*string)(unsafe.Pointer(namePtr))                    //把这个偏移量 和 结构体值在内存中的起始存储地址(dog3PointerData)相加就可以得到dog3Pointer.name字段值的起始存储地址
	fmt.Println(nameP)                                             //0xc000066e50
	fmt.Println(*nameP)                                            //泰迪
	fmt.Println(&(dog3Pointer.name))                               //0xc000066e50,虽然这样也能拿到内存地址,但是如果根本就不知道这个结构体类型是什么，也拿不到dog3Pointer这个变量呢？

	println()

	//指针的指针
	a := 10
	var p1 *int
	var p2 **int
	fmt.Println(p2) //<nil>
	p2 = &p1
	fmt.Printf("%T,%T,%T\n", a, p1, p2) //int, *int, **int
	fmt.Println("p2的数值,也就是p1的地址：", p2)  //0xc000120020
	fmt.Printf("p2的地址：%p\n", &p2)       //0xc000120028
}
